// Copyright (C) 2023 即时通讯网(52im.net) & Jack Jiang.
// The RainbowChat Project. All rights reserved.
// 
// 【本产品为著作权产品，合法授权后请放心使用，禁止外传！】
// 【本次授权给：<北京小羊驼科技有限公司>，授权编号：<NT231212144350>，代码指纹：<A.702363430.550>，技术对接人微信：<ID: wxid_wbyootvkdcgj22>】
// 【授权寄送：<收件：苏先生、地址：北京市大兴区北京密码西区6号楼1单元301、电话：18613885610、邮箱：bd@huiyuanxiang-inc.com>】
// 
// 【本系列产品在国家版权局的著作权登记信息如下】：
// 1）国家版权局登记名(简称)和权证号：RainbowChat    （证书号：软著登字第1220494号、登记号：2016SR041877）
// 2）国家版权局登记名(简称)和权证号：RainbowChat-Web（证书号：软著登字第3743440号、登记号：2019SR0322683）
// 3）国家版权局登记名(简称)和权证号：RainbowAV      （证书号：软著登字第2262004号、登记号：2017SR676720）
// 4）国家版权局登记名(简称)和权证号：MobileIMSDK-Web（证书号：软著登字第2262073号、登记号：2017SR676789）
// 5）国家版权局登记名(简称)和权证号：MobileIMSDK    （证书号：软著登字第1220581号、登记号：2016SR041964）
// * 著作权所有人：江顺/苏州网际时代信息科技有限公司
// 
// 【违法或违规使用投诉和举报方式】：
// 联系邮件：jack.jiang@52im.net
// 联系微信：hellojackjiang
// 联系QQ号：413980957
// 授权说明：http://www.52im.net/thread-1115-1-1.html
// 官方社区：http://www.52im.net
package com.x52im.rainbowchat.http.logic;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Vector;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.eva.epc.common.file.FileHelper;
import com.eva.epc.common.util.CommonUtils;
import com.eva.epc.common.util.RestHashMap;
import com.eva.framework.Processor;
import com.eva.framework.dbpool.DBDepend;
import com.eva.framework.dbpool.DBShell;
import com.eva.framework.dto.SysActionConst;
import com.eva.framework.utils.EndsExceptionFactory;
import com.eva.framework.utils.LoggerFactory;
import com.x52im.rainbowchat.BaseConf;
import com.x52im.rainbowchat.MyControllerJSONP;
import com.x52im.rainbowchat.cache.GroupsInfoCacheProvider;
import com.x52im.rainbowchat.cache.GroupsMembersCacheProvider;
import com.x52im.rainbowchat.cache.UsersInfoCacheProvider;
import com.x52im.rainbowchat.http.logic.dto.OfflineMsgDTO;
import com.x52im.rainbowchat.http.logic.dto.RosterElementEntity;
import com.x52im.rainbowchat.http.logic.dto.UserRegisterDTO;
import com.x52im.rainbowchat.http.util.ip.IPSeeker;
import com.x52im.rainbowchat.http.util.ip._Utils;
import com.x52im.rainbowchat.im.ChatServerEventListener;
import com.x52im.rainbowchat.im.dto.CMDBody4AddFriendRequest;
import com.x52im.rainbowchat.im.dto.CMDBody4ProcessFriendRequest;
import com.x52im.rainbowchat.im.dto.MsgBodyRoot;
import com.x52im.rainbowchat.im.impl.ChatLogicManager;
import com.x52im.rainbowchat.im.impl.ChatLogicManager4Group;
import com.x52im.rainbowchat.im.util.MessageHelper;
import net.x52im.mobileimsdk.server.protocal.Protocal;

/**
 * 基本Http业务接口处理器实现类。
 * 
 * @author Tony ma, Jack Jiang
 */
public class LogicProcessor2 implements SysActionConst, Processor
{
	public static DBShell db = new DBShell();
	
	/**
	 * 处理器的核心实现方法.<br>
	 * 可据job_dispatch_id找到对应的JobDispatcher，JobDispatcher又能据
	 * action_id进行对应动作的处理，以完成本次操作请求.
	 * 
	 * @param job_dispatch_id 作业调度ID
	 * @param action_id 动作ID
	 * @param Object newData 客端发送过来的本次修改新数据(可能为空，理论上与oldData不会同时空）
	 * @param Object oldData 客端发送过来的本次修改前的老数据(可能为空，理论上与newData不会同时空）
	 * @param UserRoot user 包含该请求用户完整个人信息封装对象（该对象来自于成功登陆后存放于session中的值）
	 * @return 任意类型返回值
	 * @throws Exception
	 */
	@Override public Object process(int job_dispatch_id, int action_id, Object _newData, Object _oldData, User user)
			throws Exception
	{
		Object ret = null;
		String newDataJSON = (String)_newData;
		
		switch (job_dispatch_id)
		{	
			// 处理用户注册业务逻辑
			case com.x52im.rainbowchat.http.logic.dto.JobDispatchConst.LOGIC_REGISTER:// ok
				ret = maintainRegisterJobDispatcher(action_id, newDataJSON, _oldData , user);
				break;

			// 好友管理逻辑
			case com.x52im.rainbowchat.http.logic.dto.JobDispatchConst.LOGIC_ROSTER:// ok
				ret = maintainRosterMgrJobDispatcher(action_id, newDataJSON, _oldData , user);
				break;
				
			// 社交关系管理逻辑
			case com.x52im.rainbowchat.http.logic.dto.JobDispatchConst.LOGIC_SNS:// ok
				ret = maintainSNSJobDispatcher(action_id, newDataJSON, _oldData, user);
				break;
			// 消息管理逻辑
			case com.x52im.rainbowchat.http.logic.dto.JobDispatchConst.LOGIC_MESSAGES:
				ret = maintainMessagesJobDispatcher(action_id, newDataJSON, _oldData , user);
				break;
			// 删除好友
			case com.x52im.rainbowchat.http.logic.dto.JobDispatchConst.LOGIC_DELETE$FRIEND:// ok
				ret = maintainDeleteFriendJobDispatcher(action_id, newDataJSON, _oldData , user);
				break;
			// 处理个人相册、个人介绍语音留言等个人详细信息资源的管理
			case com.x52im.rainbowchat.http.logic.dto.JobDispatchConst.LOGIC_MGR$PROFILE:
				ret = maintainMgrProfileJobDispatcher(action_id, newDataJSON, _oldData , user);
				break;
				
			// 处理通用数据查询接口逻辑的管理
			// 说明：除了因历史原因已散落在其它区的查询代码，新增加的查询接口都应该放到本管理器里
			case com.x52im.rainbowchat.http.logic.dto.JobDispatchConst.LOGIC_COMMON_QUERY_MGR:
				ret = maintainCommonQueryJobDispatcher(action_id, newDataJSON, _oldData , user);
				break;
				
			default:
				throw EndsExceptionFactory.INVALID_JOB_DISPATCHER_ID(job_dispatch_id);
		}
		return ret;
	}
	
	/** 
	 * 维护注册管理.
	 * 
	 * @param action_id 维护类型，SysConstant中的操作类型常量
	 * @param newData 客端发送过来的本次修改新数据(可能为空，理论上与oldData不会同时空）
	 * @param oldData 客端发送过来的本次修改前的老数据(可能为空，理论上与newData不会同时空）
	 * @param user 操作者的用户名
	 * @return 返回给客户端的对象
	 */
	public String maintainRegisterJobDispatcher(int action_id
			, Object newData, Object oldData, User user) throws Exception
	{
		Vector sqls = new Vector(2);
		Vector values = new Vector(2);
		
		switch (action_id)
		{
			// 【接口1008-1-7】完成注册过程
			// 》当前正在被使用于：APP端、Web端
			case ACTION_APPEND1:
			{
				UserRegisterDTO urd = JSON.parseObject((String)newData, UserRegisterDTO.class);
				
				String newUidForReturn = null;
				
				String sql = "SELECT user_uid FROM missu_users WHERE user_mail = '" + urd.getUser_mail() + "'";
				String existMail = db.querySingleItem(sql);
				// 本次注册邮件不存在
				if(CommonUtils.isStringEmpty(existMail))
				{
					sqls.add("INSERT INTO missu_users(user_mail,nickname,user_psw,user_sex" +
							",register_ip,user_regieon,register_time) VALUES(?,?,?,?,?,?,"+DBDepend.getDefaultDatetimeFunc()+")");
					values.add(new Object[]{urd.getUser_mail(),urd.getNickname()
							,CommonUtils.encyptPassword(urd.getUser_psw(), ""),urd.getUser_sex()
							,user.getIp(),_Utils.checkChinaArea(IPSeeker.getInstance().getCountry(user.getIp()))});
					db.update(sqls, values, true);
					
					// 注册完成后将UID返回给客户端
					String uid = db.querySingleItem("user_uid", "missu_users", "user_mail='"+urd.getUser_mail()+"'");
					
					// TODO 以下代码仅用于自动添加演示数据，可以删除 START -------------------------------
					if(BaseConf.getInstance().getAUTO_PREPARED_DEMO_DATAS()) {
						LoggerFactory.getLog().info("已开启\"自动准备演示数据\"的开关，将为新用户"+uid+"准备之！");

						try {
							Vector<String> sqlsForExp = new Vector<>();
							Vector<Object> valuesForExp = new Vector<>();

							//**【1】为了方便测试礼品的使用（需要积分），为新注册的测试用户自动提供积分（体供1000积份供测试体验）
							sqlsForExp.add("INSERT INTO token_user_token(user_uid, current_token_count) values(?,?)");
							valuesForExp.add(new Object[]{uid, "1000"});

							//**【2】插入一条用于体验“加好友请求”功能的请求记录
							sqlsForExp.add("insert into missu_friendreq(user_uid,be_user_uid,be_desc,bt_ip,be_time,be_timestamp) " +
									"values(?,?,?,?,"+DBDepend.getDefaultDatetimeFunc()+",?)");
							valuesForExp.add(new Object[]{"400070", uid
											, "此请求用于体验加好友功能，由系统自动生成，您可选择\"同意\"或\"拒绝\"哦。"
											, "192.168.0.190", ""+System.currentTimeMillis()});

							//**【3】插入一条用于体验好友关系的好友记录（方便体验好友聊天功能）
							// 插入B的好友记录（好友是A）
							sqlsForExp.add("INSERT INTO missu_roster(user_uid,friend_user_uid,add_time) " + "VALUES(?,?,"+DBDepend.getDefaultDatetimeFunc()+")");
							valuesForExp.add(new Object[]{uid,"400069"});
							// 插入A的好友记录（好友是B）
							sqlsForExp.add("INSERT INTO missu_roster(user_uid,friend_user_uid,add_time) " + "VALUES(?,?,"+DBDepend.getDefaultDatetimeFunc()+")");
							valuesForExp.add(new Object[]{"400069",uid});

							//**【4】插入一条用于体验“好友聊天”功能的离线消息（发送者：400069）
							sqlsForExp.add("INSERT INTO missu_offline_history(user_uid,friend_user_uid, chat_type,history_type" +
											",history_content,history_content2,history_time2,history_time,group_id,parent_fp)" +
											" VALUES(?,?,?,?,?,?,?,"+DBDepend.getDefaultDatetimeFunc()+",?,?)");
							valuesForExp.add(new Object[]{"400069", uid, MsgBodyRoot.CHAT_TYPE_FREIDN$CHAT, MsgBodyRoot.TYPE_TEXT
											, "你好，我是作者Jack Jiang，欢迎使用RainbowChat。\n\n"
											  +"此版本仅限于体验RainbowChat技术能力和功能品质之用，请勿他用（尤其禁止用于违法业务的沟通）。\n\n"
//											  +"如需联系我，可加微信：hellojackjiang、QQ：413980957。\n\n"
									          +"如需联系我，可点链接：http://www.52im.net/thread-2792-1-1.html"
											, Protocal.genFingerPrint(), ""+System.currentTimeMillis(), null, null});

							//**【5】插入一条用于体验“陌生人聊天”功能的离线消息（发送者：400086）
							sqlsForExp.add("INSERT INTO missu_offline_history(user_uid,friend_user_uid, chat_type,history_type" +
									",history_content,history_content2,history_time2,history_time,group_id,parent_fp)" +
									" VALUES(?,?,?,?,?,?,?,"+DBDepend.getDefaultDatetimeFunc()+",?,?)");
							valuesForExp.add(new Object[]{"400086", uid, MsgBodyRoot.CHAT_TYPE_GUEST$CHAT, MsgBodyRoot.TYPE_TEXT
									, "Hi，这是一条陌生人聊天消息，仅为方便你体验陌生人聊天之用。\n\n"
									  + "陌生人聊天时，双方无需互加好友，也互相不在对方的好友列表中，适用于不需要添加好友的功能场景中（比如客服系统等）。\n\n"
									  + "陌生人聊天时，默认是不能进行实时音视频聊天的（但这只是人为限制的，如有必要，在二次开发时完全可以开放出来）。"
									, Protocal.genFingerPrint(), ""+System.currentTimeMillis(), null, null});

							//**【6】插入一条用于体验“系统通知”功能的离线消息（发送者：10000）
							sqlsForExp.add("INSERT INTO missu_offline_history(user_uid,friend_user_uid, chat_type,history_type" +
									",history_content,history_content2,history_time2,history_time,group_id,parent_fp)" +
									" VALUES(?,?,?,?,?,?,?,"+DBDepend.getDefaultDatetimeFunc()+",?,?)");
							valuesForExp.add(new Object[]{"10000", uid, MsgBodyRoot.CHAT_TYPE_GUEST$CHAT, MsgBodyRoot.TYPE_TEXT
									, "欢迎你的到来，这是一条由系统自动发送的消息。\n\n"
									+ "产品介绍和资料链接，请点击：http://rbpdocs40415.52im.net:7080/rainbowchat_pro/clause/qna_cn.html\n\n"
									+ "当前部署仅用于体验RainbowChat产品品质之用，为防被利用，所有聊天记录都会被记录，请不要作为私人聊天工具跟朋友真实交流。\n\n"
									+ "如遇到任何技术、产品或授权上的疑问，请联系Jack Jiang作进一步交流，感谢你的支持！\n\n"
									, Protocal.genFingerPrint(), ""+System.currentTimeMillis(), null, null});

							db.update(sqlsForExp, valuesForExp, true);
						} catch (Exception ee) {
							LoggerFactory.getLog().warn(ee.getMessage(), ee);
						}
					}
					// TODO 以下代码仅用于自动添加演示数据，可以删除 END   -------------------------------
					
					//** 【高速缓存】即时更新用户缓存数据（因为新加了用户）
					UsersInfoCacheProvider.getInstance().reloadOneFromDB(uid);
					
					newUidForReturn = uid;
				}
				// 返回值“0”表示注册邮件已经存在，本次注册失败！
				else
				{
					newUidForReturn = "0";
				}
				
				//** 返回值
				return JSON.toJSONString(RestHashMap.n().p("new_uid", newUidForReturn));
			}
			
			// 【接口1008-1-8】修改个人信息，包括昵称、性别
			// 》当前正在被使用于：APP端
			case ACTION_APPEND2:
			{	
				JSONObject nwObj = JSONObject.parseObject((String)newData);
				
				sqls.add("UPDATE missu_users SET nickname=?,user_sex=? WHERE user_uid=?");
				values.add(new Object[] { nwObj.getString("nickName")
						, nwObj.getString("sex"), nwObj.getString("uid")});
				boolean res = db.update(sqls, values, true);
				
				//** 【高速缓存】即时更新用户缓存数据（因为更新了用户的昵称）
				UsersInfoCacheProvider.getInstance().reloadOneFromDB(nwObj.getString("uid"));
				
				return res?"1":"0";// 1 表示更新成功，否则失败
			}
			// 【接口1008-1-9】修改登陆密码
			// 》当前正在被使用于：APP端、Web端
			case ACTION_APPEND3:
			{	
				JSONObject nwObj = JSONObject.parseObject((String)newData);
				
				// 返回值：1 表示更新成功，0 表示失败，2 表示原密码不正确
				String ret = "0";
				
				String sql = "SELECT user_psw FROM missu_users WHERE user_uid = '" 
						+ nwObj.getString("uid")
						+"'";
				String oldPsw = db.querySingleItem(sql);
				if(!CommonUtils.isStringEmpty(oldPsw))
				{
					if(oldPsw.equals(CommonUtils.encyptPassword(nwObj.getString("old_psw"), "")))
					{
						sqls.add("UPDATE missu_users SET user_psw=? WHERE user_uid=? AND user_psw=?");
						values.add(new Object[] {
								CommonUtils.encyptPassword(nwObj.getString("psw"), "")
								, nwObj.getString("uid")
								, CommonUtils.encyptPassword(nwObj.getString("old_psw"), "")
						});
						boolean res = db.update(sqls, values, true);
						ret = res?"1":"0";
					}
					else
					{
						ret = "2";
					}
				}
				return ret;
			}
			// 【接口1008-1-22】更新个性签名（what's up）
			// 》当前正在被使用于：APP端
			case ACTION_APPEND4:
			{	
				JSONObject nwObj = JSONObject.parseObject((String)newData);
				sqls.add("UPDATE missu_users SET what_s_up=? WHERE user_uid=?");
				values.add(new Object[] { nwObj.getString("whats_up"), nwObj.getString("uid") });
				boolean res = db.update(sqls, values, true);
				return res?"1":"0";// 1 表示更新成功，否则失败
			}
			// 【接口1008-1-24】更新个人其它说明
			// 》当前正在被使用于：APP端
			case ACTION_APPEND6:
			{	
				JSONObject nwObj = JSONObject.parseObject((String)newData);
				sqls.add("UPDATE missu_users SET user_desc=? WHERE user_uid=?");
				values.add(new Object[] { nwObj.getString("user_desc"), nwObj.getString("uid") });
				boolean res = db.update(sqls, values, true);
				return res?"1":"0";// 1 表示更新成功，否则失败
			}
			// 【接口1008-1-25】更新个人信息中的昵称、性别、个性签名、其它说明的综合接口
			// 》当前正在被使用于：Web端（当对接RainbowChat-Web产品时）
			// 》说明：本综合接口目前主要是用于Web版中的修改个人信息的功能。当然，并不限制它的使用场景，请按需使用。
			case ACTION_APPEND7:
			{	
				JSONObject nwObj = JSONObject.parseObject((String)newData);
				
				sqls.add("UPDATE missu_users SET nickname=?,user_sex=?,what_s_up=?,user_desc=? WHERE user_uid=?");
				values.add(new Object[] { 
						nwObj.getString("nickName")
						, nwObj.getString("sex")
						, nwObj.getString("whats_up")						
						, nwObj.getString("user_desc")	
						, nwObj.getString("uid")});
				boolean res = db.update(sqls, values, true);
				
				//** 【高速缓存】即时更新用户缓存数据（因为更新了用户的昵称）
				UsersInfoCacheProvider.getInstance().reloadOneFromDB(nwObj.getString("uid"));
				
				return res?"1":"0";// 1 表示更新成功，否则失败
			}
			
			default:
				throw EndsExceptionFactory.INVALID_ACTION_ID(action_id);
		}
	}
	
	/** 
	 * 维护好友管理.
	 * 
	 * @param action_id 维护类型，SysConstant中的操作类型常量
	 * @param newData 客端发送过来的本次修改新数据(可能为空，理论上与oldData不会同时空）
	 * @param oldData 客端发送过来的本次修改前的老数据(可能为空，理论上与newData不会同时空）
	 * @param user 操作者的用户名
	 * @return 返回给客户端的对象
	 */
	public String maintainRosterMgrJobDispatcher(int action_id, Object newData, Object oldData, User user) throws Exception
	{
		Vector sqls = new Vector(2);
		Vector values = new Vector(2);
		
		switch (action_id)
		{
			// 【接口1008-2-7】获取好友列表过程
			case ACTION_APPEND1://ok
			{	
				// 客户端传过来的是UID（不会是mail，因为客户端已经处理过了）
				String uid = (String)newData;
				ArrayList<RosterElementEntity> rosters = new ArrayList<RosterElementEntity>();
				
				// 该用户的好友列表
				Vector<Vector> roster = db.queryData(
						"select a.friend_user_uid,b.user_mail,b.nickname" +
						"		,b.user_sex,b.user_avatar_file_name,b.what_s_up "+
						"		,a.remark,a.mobile_num,a.more_desc,a.pic_file_name  "+
						"	from missu_roster a "+
						"	left join missu_users b on a.friend_user_uid = b.user_uid "+
						"	where a.user_uid=?", new Object[] {uid});
				// 遍历好友列表，设置好友数据
				for(Vector friendRow : roster)
				{
					int i = 0;
					// 好友uid
					String friend_user_uid = (String)friendRow.get(i++);
					String friend_user_mail = (String)friendRow.get(i++);
					String friend_nickname = (String)friendRow.get(i++);
					String friend_user_sex = (String)friendRow.get(i++);
					String user_avatar_file_name = (String)friendRow.get(i++);
					String what_s_up = (String)friendRow.get(i++);
					
					String remark = (String)friendRow.get(i++);
					String mobile_num = (String)friendRow.get(i++);
					String more_desc = (String)friendRow.get(i++);
					String pic_file_name = (String)friendRow.get(i++);
					
					RosterElementEntity friend = new RosterElementEntity();
					
					ChatServerEventListener.getInstance().getLiverUsersMgr().printLiveUsers();
					// 遍历在线好友列表，看看该好友是否在线（在线用户列表中的key可能是user_uid也可能是user_mail，
					// 因为客户端允许用户使用uid和mail二种方式登陆）
					if(ChatServerEventListener.getInstance().getLiverUsersMgr().isOnline(friend_user_uid))
					{
						// 设置在线状态为“在线”
						friend.setLiveStatus(1);
					}
					friend.setUser_uid(friend_user_uid);
					friend.setUser_mail(friend_user_mail);
					friend.setNickname(friend_nickname);
					friend.setUser_sex(friend_user_sex);
					friend.setUserAvatarFileName(user_avatar_file_name);
					friend.setWhatsUp(what_s_up);
					
					friend.setFriendRemark(remark);
					friend.setFriendMobileNum(mobile_num);
					friend.setFriendMoreDesc(more_desc);
					friend.setFriendPicFileName(pic_file_name);
					
					rosters.add(friend);
				}

				return JSON.toJSONString(rosters);
			}
			// 【接口1008-2-8】更新好友信息中的备注、描述等的接口
			// 》当前正在被使用于：Andriod端、iOS端、Web端
			case ACTION_APPEND2:
			{	
				JSONObject nwObj = JSONObject.parseObject((String)newData);
				sqls.add("UPDATE missu_roster SET remark=?,mobile_num=?,more_desc=? WHERE user_uid=? and friend_user_uid=?");
				values.add(new Object[] { nwObj.getString("remark"), nwObj.getString("mobile_num")
						, nwObj.getString("more_desc"), nwObj.getString("user_uid"), nwObj.getString("friend_user_uid")});
				boolean res = db.update(sqls, values, true);
				return res?"1":"0";// 1 表示更新成功，否则失败
			}
			default:
				throw EndsExceptionFactory.INVALID_ACTION_ID(action_id);
		}
	}
	
	/** 
	 * 维护好友关系管理.
	 * 
	 * @param action_id 维护类型，SysConstant中的操作类型常量
	 * @param newData 客端发送过来的本次修改新数据(可能为空，理论上与oldData不会同时空）
	 * @param _oldData 客端发送过来的本次修改前的老数据(可能为空，理论上与newData不会同时空）
	 * @param user 操作者的用户名
	 * @return 返回给客户端的对象
	 */
	public String maintainSNSJobDispatcher(int action_id
			, Object newData, Object _oldData, User user) throws Exception
	{
		Object[] objs = (Object[])_oldData;
		Object oldData = objs[1];
		
		Vector sqls = new Vector();
		Vector values = new Vector();
		
		switch (action_id)
		{
			// 【接口1008-3-7】完成邀请朋友注册的邮件发送
			case ACTION_APPEND1:// ok
			{	
				JSONObject nwObj = JSONObject.parseObject((String)newData);
				
				// 接收邀请的email地址
				String sendToMailAddr = nwObj.getString("receiver_mail");
				// 发起邀请人的昵称
				String fromNickName = nwObj.getString("local_nickname");
				// 发起邀请人的email（作为被邀请人加好友的凭证）
				String fromMail = nwObj.getString("local_mail");
				// 发起邀请人的uid（作为被邀请人加好友的凭证）
				String fromUid = nwObj.getString("local_uid");
				
				// 邀请邮件正文
				String inviteMainContent = BaseConf.getInstance()
						.getSNS_INVITE_MAIL_CONTENT(fromNickName, fromMail, fromUid);
				
				// * 注充说明：如果用户输入的是一个不存在的邮箱，只要邮箱格式合法，服务
				// * 端是没有办法也没有必要知道这个邮箱是否是真的存在，反正只管发就是了。
				
				// 邮件发送是个缓慢的过程，为了不影响接口调用者的体验，所以用异步线程发送
				LogicUtils.sendMailAsync(
						sendToMailAddr
						, MessageFormat.format(BaseConf.getInstance().getSNS_INVITE_MAIL_SUBJECT(),fromNickName) 
						, inviteMainContent
						, "邀请朋友");
				
				return null;
			}
			// 【接口1008-3-8】查询好友/陌生人的基本信息
			case ACTION_APPEND2:// ok
			{	
				JSONObject nwObj = JSONObject.parseObject((String)newData);
				
				// true表示用用户或女孩友的mail地址，否则表示用用户或好友的uid
				boolean useMail = "1".equals(nwObj.getString("use_mail"));
				// 用户或的mail地址
				String mailOfFriend = nwObj.getString("friend_mail");
				// 用户或好友的uid
				String uidOfFriend = nwObj.getString("friend_uid");
				
				// 查询发起人的uid，这个uid指的是客户端提起这个查询时的当前登陆者uid，
				// 指明此uid后本sql将同时提供被查询作为好友的额外信息。本参数可为null（表示不需要查询好友的额外信息）
				String myUid = nwObj.getString("my_uid");

				return JSON.toJSONString(LogicUtils.queryUserInfo(useMail, mailOfFriend, uidOfFriend, myUid));
			}
			// 【接口1008-3-9】"忘记密码"
			case ACTION_APPEND3:// ok
			{	
				// 要发送“重置密码”邮件的接收邮箱
				final String emailAddr = (String)newData;
			
				// 尝试查询此邮箱对应的注册信息
				RosterElementEntity userInfo = LogicUtils.queryUserInfo(emailAddr, null);
				
				// 邀请邮件正文
				String responseMainContent = null;
				String responseSubject = null;
				// 表示没有此邮箱的注册信息
				if(userInfo == null)
				{
					// 如果该邮箱未注册，则发送的邮件内容是错误信息，信息内会引导用户用此邮箱来注册（详见邮件模板哦）
					responseMainContent = BaseConf.getInstance()
						.getSNS_RESPONSE_FOR_FORGOTPSW_ERROR_TEMPLATE(emailAddr);
					responseSubject = BaseConf.getInstance().getSNS_RESPONSE_FORGOT_PSW_ERROR_MAIL_SUBJECT();
				}
				// 该注册邮件存在情交下，发送的邮件内容就是注册信息以及重置密码链接（详见邮件模板哦）
				else
				{
					
					String verificationCode = CommonUtils.getRandomStr2(8);
					sqls.add("UPDATE missu_users SET verification_code=?, verification_time="
							+DBDepend.getDefaultDatetimeFunc()+" WHERE user_mail=?");
					values.add(new Object[]{verificationCode, emailAddr});
					db.update(sqls, values, false);
					
					String path = objs[0] + "view/redirectToForget.jsp?loginName=" + userInfo.getUser_uid() + "&verificationCode=" + verificationCode; 
					responseMainContent = BaseConf.getInstance().getSNS_RESPONSE_FOR_FORGOTPSW_TEMPLATE(
							userInfo.getNickname(), userInfo.getUser_uid(), userInfo.getUser_mail(), verificationCode, path);
					responseSubject = BaseConf.getInstance().getSNS_RESPONSE_FORGOT_PSW_MAIL_SUBJECT();
				}
				
				// * 注充说明：如果用户输入的是一个不存在的邮箱，只要邮箱格式合法，服务
				// * 端是没有办法也没有必要知道这个邮箱是否是真的存在，反正只管发就是了。
				
				// 邮件发送是个缓慢的过程，为了不影响接口调用者的体验，所以用异步线程发送
				LogicUtils.sendMailAsync(emailAddr, responseSubject, responseMainContent, "忘记密码");
				
				return null;
			}
			// 分页加载好友（参见FindListFormActivity类，20170227已取消对应的功能，本接口暂时废弃了）
//			case ACTION_APPEND4:
//			{
//				// ** 数组的元素位置一定要与客户端保持一致哦（参见客户端FindFriendFromActivity）
//				Object[] datasFromClient = (Object[])newData;
//				
//				// 模糊查询参数，值可能是：uid,mail,nickname
//				String searchInfo = (String)datasFromClient[0];
//				// 查询好友性别条件
//				String sexOfFriend = (String)datasFromClient[1];
//				
//				String currentUserUid = (String)datasFromClient[2];
//				
//				//分页查询起始与结束
//				String beginIndex = datasFromClient[3] + "";
//				String endIndex = datasFromClient[4] + "";
//
//				String conditionSql = " and user_uid != '" + currentUserUid + "'";
//
//				conditionSql += CommonUtils.isStringEmpty(searchInfo) ? searchInfo 
//						: " and (user_uid like '%"+searchInfo+"%' or user_mail = '"
//									+searchInfo+"' or nickname like '%"+searchInfo+"%')";
//				
//				// 加上姓别查询条件
//				conditionSql += CommonUtils.isStringEmpty(sexOfFriend) ?  sexOfFriend : " and user_sex=" + sexOfFriend;
//				
//				Vector<Vector> vec = db.queryData(MessageFormat.format(USER_INFI_PAGE_SQL, conditionSql) + " where rownum between " + beginIndex + " and " + endIndex);
//				
//				ArrayList<RosterElementEntity> friendInfoList = new ArrayList<RosterElementEntity>();
//				if(vec.size() > 0)
//				{
//					for(Vector row : vec)
//					{
//						RosterElementEntity friendInfoForFound = new RosterElementEntity();
//						int i = 0;
//						friendInfoForFound.setUser_uid((String) row.get(i++));
//						friendInfoForFound.setUser_mail((String) row.get(i++));
//						friendInfoForFound.setNickname((String) row.get(i++));
//						friendInfoForFound.setUser_sex((String) row.get(i++));
//						friendInfoForFound.setRegister_time((String) row.get(i++));
//						friendInfoForFound.setLatest_login_time((String) row.get(i++));
//						i++;//查询条件中包含密码,不进行Set
//						friendInfoForFound.setLiveStatus(Integer.valueOf(row.get(i++).toString()));
//						friendInfoForFound.setUserAvatarFileName((String) row.get(i++));
//						friendInfoForFound.setWhatsUp((String) row.get(i++));
//						friendInfoForFound.setMaxFriend((String) row.get(i++));
//						friendInfoForFound.setUserDesc((String) row.get(i++));
//						friendInfoForFound.setUserType((String) row.get(i++));
//						
//						friendInfoList.add(friendInfoForFound);
//					}
//				}
//				
//				return friendInfoList;
//			}
			// 【接口1008-3-23】查找好友之“随机查找”
			case ACTION_APPEND5:// ok
			{
				JSONObject nwObj = JSONObject.parseObject((String)newData);
				
				// 本地用户的uid：用于查询结果中排除“自已”
				String user_uid = nwObj.getString("local_uid");
				// 性别查询条件：-1 表示不使用本条件，1  表只查男性，0  表只查女性
				String user_sex = nwObj.getString("sex_condition");
				// 在线状态查询条件：-1 表示不使用本条件，1  表只查在线，0 表只查离线
				String is_online = nwObj.getString("online_condition");
				
				String sql = "";
				if(DBShell.isSQLServer())
				{
					// SQL Server数据库随机取前10条记录的方法是：top 10和order by newid()即可实现
					sql = MessageFormat.format(LogicUtils.getUSER_INFI_RANDOM_SQL(user_uid)
							, "top 10"
							, user_uid
							, user_sex.equals("-1") ? "" : " and user_sex=" + user_sex
							, is_online.equals("-1") ? "" : " and is_online=" + is_online
							, "newid()");
				}
				else if(DBShell.isMySQL())
				{
					// MySQL数据库随机取前10条记录的方法是：order by rand() limit 0,10 即可实现
					sql = MessageFormat.format(LogicUtils.getUSER_INFI_RANDOM_SQL(user_uid)
							, ""
							, user_uid
							, user_sex.equals("-1") ? "" : " and user_sex=" + user_sex
							, is_online.equals("-1") ? "" : " and is_online=" + is_online
							, "rand() limit 0,10");
				}
				
				Vector<Vector> vec = db.queryData(sql);
				ArrayList<RosterElementEntity> friendInfoList = new ArrayList<RosterElementEntity>();
				if(vec.size() > 0)
				{
					for(Vector<String> row : vec)
						friendInfoList.add(LogicUtils.constructUserInfo(row.toArray()));
				}
				
				return JSON.toJSONString(friendInfoList);
			}
			
			// 【接口1008-3-24】用户A发起的添加好友请求  @since 4.5（）
			// 适用范围：Web端、Android端（待用）、iOS端（待用）
			case ACTION_APPEND6:
			{
				// 解析添加好友请求原数据（客户端A发过来的）
				CMDBody4AddFriendRequest addFriendMetaFromClient = 
						MessageHelper.pareseAddFriendRequestMessage((String)newData);
				// 进入处理逻辑并返回结果
				ChatLogicManager.processMT05_OF_ADD_FRIEND_REQUEST_A$TO$SERVER(addFriendMetaFromClient);
				return null;
			}
			// 【接口1008-3-25】被添加者【同意】加好友请求的处理接口（此处理是由被加者发起的）   @since 4.5
			// 适用范围：Web端、Android端（待用）、iOS端（待用）
			case ACTION_APPEND7:
			{
				// 解析被添加者同意加好友请求原数据（客户端A发过来的）
				CMDBody4ProcessFriendRequest processFriendMetaFromClient = 
						MessageHelper.pareseProcessAdd$Friend$Req_B$To$Server_AGREEMessage((String)newData);
				// 进入处理逻辑并返回结果
				ChatLogicManager.processMT08_OF_PROCESS_ADD$FRIEND$REQ_B$TO$SERVER_AGREE(processFriendMetaFromClient);
				return null;
			}
			// 【接口1008-3-26】被添加者【拒绝】加好友请求的处理接口（此处理是由被加者发起的）   @since 4.5
			// 适用范围：Web端、Android端（待用）、iOS端（待用）
			case ACTION_APPEND8:
			{
				// 解析被添加者拒绝加好友请求原数据（客户端A发过来的）
				CMDBody4ProcessFriendRequest processFriendMetaFromClient = 
						MessageHelper.pareseProcessAdd$Friend$Req_B$To$Server_REJECTMessage((String)newData);
				// 进入处理逻辑并返回结果
				ChatLogicManager.processMT09_OF_PROCESS_ADD$FRIEND$REQ_B$TO$SERVER_REJECT(processFriendMetaFromClient);
				return null;
			}
			
			default:
				throw EndsExceptionFactory.INVALID_ACTION_ID(action_id);
		}
	}
	
	/** 
	 * 维护消息管理.
	 * 
	 * @param action_id 维护类型，SysConstant中的操作类型常量
	 * @param newData 客端发送过来的本次修改新数据(可能为空，理论上与oldData不会同时空）
	 * @param oldData 客端发送过来的本次修改前的老数据(可能为空，理论上与newData不会同时空）
	 * @param user 操作者的用户名
	 * @return 返回给客户端的对象
	 */
	public String maintainMessagesJobDispatcher(int action_id
			, Object newData, Object oldData, User user) throws Exception
	{
		switch (action_id)
		{
			// 【接口1008-4-7】获取离线状态下好友发过来的加好友请求列表
			// FIXME: 性能优化点：以下查询其实可以一条SQL搞定，但目前为了简化字段解析代码而如此使用，以后再考虑进行查询优化（合并查询）
			case ACTION_APPEND1:// ok
			{	
				ArrayList<RosterElementEntity> allRequestFriendsInfo = new ArrayList<RosterElementEntity>();
				
				String localUid = (String)newData;
				StringBuffer sb = new StringBuffer();
				sb.append("select a.user_uid,a.be_timestamp,a.be_desc from missu_friendreq a ")
				  .append("join missu_users b on a.be_user_uid=b.user_uid ")
			      .append("where b.user_uid="+localUid+" order by a.be_time desc");
				
				Vector<Vector> vec = db.queryData(sb.toString());
				if(vec.size() > 0)
				{
					for(Vector row : vec)
					{
						// 解析行数据并构建对象
						RosterElementEntity ree = constructOfflineAddFriendReqObj(row);
						if(ree != null)
							allRequestFriendsInfo.add(ree);
					}
				}
				
				return JSON.toJSONString(allRequestFriendsInfo);
			}
			// 【接口1008-4-8】获取离线聊天消息
			case ACTION_APPEND2:
			{	
				ArrayList<OfflineMsgDTO> allRequestFriendsInfo = new ArrayList<OfflineMsgDTO>();
				
				JSONObject nwObj = JSONObject.parseObject((String)newData);
				// 离线消息所有者的uid
				String user_uid = nwObj.getString("user_uid");
				// 离线消息由谁发送的uid（此参数为null则表示读取离线消息所有者的所有
				// 离线消息，否则表示只取指定的发送人发的离线消息）
				String from_user_uid = nwObj.getString("from_user_uid");
				// 离线消息中是否带上该消息在实时发送时的指纹码（目前仅在ios端、Andriod端需要
				// ，其它端暂未用到，请忽略本参数或传“0”即可）
				String includeFingerPring = nwObj.getString("include_fp");
				boolean isIncludeFingerPring = "1".equals(includeFingerPring);
				
				StringBuilder sb = new StringBuilder()
				.append("select ")
				.append("	a.user_uid" )
				.append("   ,b.nickname" )
//				.append("	,b.user_mail" )
				.append("	,a.friend_user_uid" )
//				.append("	,c.user_mail" )
				.append("	,a.history_type" )
				.append("	,a.history_content" )
//				.append("	,a.history_content2" )
				.append("	,a.history_time2")
				.append("	,a.chat_type")
				.append("	,a.group_id" )  // 只对群聊消息有意义（且只在群聊消息时不为null）
				.append("	,d.g_name" )    // 只对群聊消息有意义
				.append(isIncludeFingerPring?",a.history_content2 as fp":",null as fp")
				.append("	,a.parent_fp" ) // 只对群聊消息有意义
				.append(" from missu_offline_history a " )
				.append(" left join missu_users b on a.user_uid = b.user_uid " )
//				.append(" left join missu_users c on a.friend_user_uid = c.user_uid " +
				.append(" left join group_base d on a.group_id = d.g_id " )
				.append(" where a.friend_user_uid = '" + user_uid + "'")
							// 为null则表示读取离线消息所有者的所有离线消息，否则表示只取指定的发送人发的离线消息
				.append(	(from_user_uid == null?"":" and a.user_uid='"+from_user_uid+"'"))
				.append(" order by a.history_time");
				 
				 String sql = sb.toString();
				
				Vector<Vector> vec = db.queryData(sql);
				if(vec.size() > 0)
				{
					for(Vector row : vec)
					{
						OfflineMsgDTO tempChatMsg = new OfflineMsgDTO();
						int i = 0;
						tempChatMsg.setUser_uid((String) row.get(i++));
						tempChatMsg.setNickName((String) row.get(i++));
//						tempChatMsg.setUser_mail((String) row.get(i++));
						tempChatMsg.setFriend_user_uid((String) row.get(i++));
//						tempChatMsg.setFriendUserMail((String) row.get(i++));
						tempChatMsg.setMsg_type(Integer.valueOf((String) row.get(i++)));
						tempChatMsg.setMsg_content((String) row.get(i++));
//						tempChatMsg.setMsg_content2((String) row.get(i++));
						tempChatMsg.setHistory_time2((String) row.get(i++));
						tempChatMsg.setChat_type((String) row.get(i++));
						tempChatMsg.setGroup_id((String) row.get(i++));
						tempChatMsg.setGroup_name((String) row.get(i++));
						
						tempChatMsg.setMsg_content2((String) row.get(i++));
						tempChatMsg.setParent_fp((String) row.get(i++));
						
						allRequestFriendsInfo.add(tempChatMsg);
					}
				}
				
				Vector sqls = new Vector();
				Vector values = new Vector();
				// 获取完离线消息后，就需要把水从离线列表中删除了哦
				sqls.add("delete from missu_offline_history where friend_user_uid = ?"
						// 为null则表示读取离线消息所有者的所有离线消息（不区分好友），
						// 否则表示只取指定的发送人发的离线消息
						+(from_user_uid == null?"":" and user_uid='"+from_user_uid+"'"));
				values.add(new Object[]{user_uid});
				
				db.update(sqls, values, false);
				
				return JSON.toJSONString(allRequestFriendsInfo);
			}
			// 【接口1008-4-9】获取未处理的加好友请求总数
			// ·版本信息：since v4.5
			// ·用途说明：用于从服务端读取未处理的加好友请求总数量；
			// ·适用范围：当对接RainbowChat-Web产品时，本接口将被用于Web端。本接口后绪也将
			//           被用于替换现版Android和iOS里读取未处理好友总数的实现代码，当前暂未使用；
			case ACTION_APPEND3:
			{	
				String localUid = (String)newData;
				
				//** 未处理的加好友请求总数量
				StringBuffer sb = new StringBuffer();
				sb.append("select count(a.user_uid) as cnt from missu_friendreq a ")
				  .append("join missu_users b on a.be_user_uid=b.user_uid ")
				  .append("where b.user_uid=?");
				// 提示：请坚持使用预编译SQL语句，不够精简，但JDBC会自动处理SQL注入风险，保证安全性！
				Vector<Vector> vecs = db.queryData(sb.toString(), new Object[]{localUid});
				// 解析查询结果
				String cnt = (String)(vecs.get(0).get(0));//db.querySingleItem(sb.toString());
				int cntInt = CommonUtils.getIntValue(cnt, 0);
				
				//** 取出最后一个加好友的请求数据（方便客户端的ui上显示）
				boolean isSQLServer = DBShell.isSQLServer();
				boolean isMySQL = DBShell.isMySQL();
				StringBuffer sb2 = new StringBuffer();
				sb2.append("select "+(isSQLServer?"top 1":"")+" a.user_uid,a.be_timestamp,a.be_desc from missu_friendreq a ")
				  .append("join missu_users b on a.be_user_uid=b.user_uid ")
			      .append("where b.user_uid=? order by a.be_time desc "+(isMySQL?"limit 0,4":""));
				Vector<Vector> vecs2 = db.queryData(sb2.toString(), new Object[]{localUid});
				// 解析行数据并构建对象
				RosterElementEntity offlineAddFriendReqObj = null;
				if(vecs2.size() > 0)
					offlineAddFriendReqObj = constructOfflineAddFriendReqObj(vecs2.get(0));
				
				//** 返回给客户端的数据
				RestHashMap<String, Object> m = RestHashMap.n();
				// 未处理的请求总数
				m.p("req_count", String.valueOf(cntInt));
				// 最近一个加好友请求的数据封装对象
				if(offlineAddFriendReqObj != null)
					m.putField("latest_req_obj", ""+JSON.toJSONString(offlineAddFriendReqObj));
				
				return JSON.toJSONString(m);
			}
			
			// 【接口1008-4-22】:删除个人全部聊天消息记录（已用于：Web端，目前其它端暂时用不上本接口）  - @since 4.5
			//  支持：删除一对一好友或陌生人聊天记录（即删除“我”与指定uid用户的记录）、群聊记录
			// ·用途说明：用于删除服务端存储的与某人或某群的全部聊天记录；
			// ·适用范围：当对接RainbowChat-Web产品时，本接口将被用于Web端（特别注意：因Android
			//           和iOS这类富客户，聊天记录都是存本地的，所以严格来说移动端不需要也没必要使用
			//           接口，本接口完全是为了对接Web产品时为其准备的）；
			case ACTION_APPEND4:
			{
				// 客户端传过来的数据
				JSONObject nwObj = JSONObject.parseObject((String)newData);

				String gid = nwObj.getString("gid");   // 删除群聊记录必填参数：要删除聊天记录的群id

				String luid = nwObj.getString("luid"); // 删除单聊和群聊记录必填参数：local uid（即“我”的uid）
				String ruid = nwObj.getString("ruid"); // 删除单聊记录选填参数：remote uid（即“对方”的uid）

				// true表示当前要删除的是群聊消息记录，否则表示要删除的一对一聊天记录
				boolean isDeleteGroupChatting = (!CommonUtils.isStringEmpty(gid, true));

				Vector sqls = new Vector();
				Vector values = new Vector();
				
				String ret = "0"; // 返回值：“0”表示失败、“1”表示成功
				
				try
				{
					// 要“删除”的是群聊聊天记录
					// 说明：群聊消息不是真的从物理上删除，只是逻辑上实现“删除”而已
					if(isDeleteGroupChatting)
					{
						// 更新此人在群内的消息起始时间即可实现消息的逻辑“删除”
						sqls.add("update group_members set msg_time_start="+DBDepend.getDefaultDatetimeFunc()+" where g_id=? and user_uid=?");
						values.add(new Object[]{gid, luid});
						
						// 提交数据库执行
						// Bug FIX：存在一种情况，即当群主解散群后，群成员未退出应用的情况下从首页“消息”列表删除时，因群已解散而不在该群中，
						//          则此update影响行数为0，如果rollbackIfZero参数设true的话，此处就会向客户端抛异常而致客户无法删除该条item
						db.update(sqls, values, false);
					}
					// 要“删除”的是单聊聊天记录
					else
					{
						//********************* 先尝试存档聊天记录和删除未拉取的离线消息 *********//
						LogicProcessor2.prepareDeleteSingleChattingMsg(sqls, values, luid, ruid);
						
						// 提交数据库执行
						db.update(sqls, values, false);
					}
					
					ret = "1"; // 成功
				}
				catch (Exception e)
				{
					LoggerFactory.getLog().warn("发生在删除与某人或某群的全部聊天记录时：", e);
				}

				return ret;
			}
			// 【接口1008-4-23】删除单条聊天消息记录（已用于：Web端，目前其它端暂时用不上本接口）  - @since 7.3
			// ·特别说明：本接口是真正的物理删除，不是转储哦！
			// ·用途说明：用于删除服务端存储的与某人或某群的单条聊天记录；
			// ·适用范围：当对接RainbowChat-Web产品时，本接口将被用于Web端（特别注意：因Android
			//           和iOS这类富客户，聊天记录都是存本地的，所以严格来说移动端不需要也没必要使用
			//           接口，本接口完全是为了对接Web产品时为其准备的）；
			case ACTION_APPEND5:
			{	
				LoggerFactory.getLog().debug("==【【【【【【【正在删除单条聊天消息。。。】】】】】】】==");
				JSONObject nwObj = JSONObject.parseObject((String)newData);
				String fpForMessage = nwObj.getString("fp_for_message");
				
				Vector sqls = new Vector();
				Vector values = new Vector();
				sqls.add("delete from missu_user_msgs_collect where fingerprint=?");
				values.add(new Object[] {fpForMessage});
				boolean res = db.update(sqls, values, false);
				return res?"1":"0";// 1 表示更新成功，否则失败
			}

			default:
				throw EndsExceptionFactory.INVALID_ACTION_ID(action_id);
		}
	}
	
	/** 
	 * 维护删除好友.
	 * 
	 * @param action_id 维护类型，SysConstant中的操作类型常量
	 * @param newData 客端发送过来的本次修改新数据(可能为空，理论上与oldData不会同时空）
	 * @param oldData 客端发送过来的本次修改前的老数据(可能为空，理论上与newData不会同时空）
	 * @param userName 操作者的用户名
	 * @return 返回给客户端的对象
	 */
	public String maintainDeleteFriendJobDispatcher(int action_id
			, String newDataJSON, Object oldData, User user) throws Exception
	{
		switch (action_id)
		{
			// 【接口1008-5-7】删除好友
			case ACTION_APPEND1:// ok
			{	
				// 客户端传过来的数据
				JSONObject nwObj = JSONObject.parseObject(newDataJSON);
				
				String myUid = nwObj.getString("local_uid");
				String friendUid = nwObj.getString("friend_uid");
				
				Vector sqls = new Vector();
				Vector values = new Vector();
				
				
				//********************* 先尝试存档聊天记录和删除未拉取的离线消息 *********//
				LogicProcessor2.prepareDeleteSingleChattingMsg(sqls, values, myUid, friendUid);
				
				
				//********************* 最后尝试删除好友关系记录 ************************//
				// 先把好友从我的好友列表中删除
				sqls.add("delete from missu_roster where user_uid=? and friend_user_uid=?");
				values.add(new Object[]{myUid, friendUid});
				// 再把我从好友的列表中删除
				sqls.add("delete from missu_roster where user_uid=? and friend_user_uid=?");
				values.add(new Object[]{friendUid, myUid});
				
				
				// 删除好友时，最佳逻辑是提前查询一下db看看是不是已被对主提前把“我”这个好友删除了，
				// 但为了性能——节省一次db查询，此处直接让它执行db删除插件，如果发生“影响行数为0”
				// 时即表示“我”已被对方提前删除。try catch的目的就是让此db执行效果静默执行，本来
				// 也不影响什么，防止将无意义的Exception发回给用户，影响体验
				try
				{
					db.update(sqls, values, true);// Bug FIX 20170406：真的发生了只删除了一方好友信息。所以现改为必须事务，二条记录必须保证要么全在要么全不在
				}
				catch(Exception e)
				{
					LoggerFactory.getLog().debug("[HTTP]在为用户"+myUid+"删除好友"+friendUid
							+"时进入了非正常路径（\""+e.getMessage()+"\"），原因可能是已提前被对方"
							+"从好友列表中删除。（本行log出现不影响正常逻辑，请忽略之）");
				}
				
				return null;
			}
			
			default:
				throw EndsExceptionFactory.INVALID_ACTION_ID(action_id);
		}
	}
	
	/** 
	 * 维护个人相册、个人介绍语音留言等个人详细信息资源的管理.
	 * 
	 * @param action_id 维护类型，SysConstant中的操作类型常量
	 * @param newData 客端发送过来的本次修改新数据(可能为空，理论上与oldData不会同时空）
	 * @param oldData 客端发送过来的本次修改前的老数据(可能为空，理论上与newData不会同时空）
	 * @param userName 操作者的用户名
	 * @param userForUploadPVoice true表示上传的是个我介绍的语音文件
	 * @return 返回给客户端的对象
	 */
	public Object maintainMgrProfileJobDispatcher(int action_id
			, String newDataJSON, Object oldData, User user) throws Exception
	{
		switch (action_id)
		{
			// 【接口1008-10-7】删除个人相册、个人介绍语音留言等个人详细信息资源，@since KChat_2.5
			case ACTION_APPEND1:
			{
				// 客户端传过来的数据
				JSONObject nwObj = JSONObject.parseObject(newDataJSON);

				String resource_id = nwObj.getString("resourceId");
				String fileName = nwObj.getString("fileName");
				String resType = nwObj.getString("resType");

				if(CommonUtils.isStringEmpty(resource_id, true)){
					throw new IllegalArgumentException("[HTTP]无效的参数, resource_id=null!");
				}

				String dir = null;
				// 要删除的是相册
				if("0".equals(resType))
					dir = BaseConf.getInstance().getDIR_USER_PHOTO_UPLOAD();
				// 要删除的是个人介绍语音文件
				else if("1".equals(resType))
					dir = BaseConf.getInstance().getDIR_USER_PVOICE_UPLOAD();
				else
					throw new IllegalArgumentException("[HTTP]无效的参数, resType="+resType);

				boolean fileDeleteSucess = false;
				// 先删除相片（原图）、语音文件
				fileDeleteSucess = FileHelper.deleteFile(dir + fileName);
				// 如果要删除的是相片则还要删除缩略图
				if("0".equals(resType))
					// 再删除相片的缩略图
					FileHelper.deleteFile(dir + "th_"+fileName);

				// 再去删除数据库记录
				if(fileDeleteSucess)
				{
					LoggerFactory.getLog().debug("[HTTP]成功删除了用户的个人资料资源文件：" + (dir + fileName));
					LogicProcessor2.db.update("delete from missu_users_binary where resource_id=?", new Object[]{resource_id}, false);
				}
				return null;
			}
			// 【接口1008-10-8】查询个人相册、个人介绍语音留言的数量（目前用于客户端个
			// 人信息查看界面中显示照片和语音预览数量时使用），@since 4.0
			case ACTION_APPEND2:
			{
				JSONObject nwObj = JSONObject.parseObject(newDataJSON);
				String uid = nwObj.getString("user_uid");// 要查询用户的uid
				
				Vector<Vector> vecs = db.queryData("select count(*), res_type from missu_users_binary where user_uid=" +
							uid+" and res_type in(0,1) group by res_type");
				return JSON.toJSONString(vecs);
			}
			// 【接口1008-10-9】查询个人相册、个人介绍语音留言的完整数据列表（目前用于客户端个
			// 人信息查看界面中显示照片和语音完整列表时使用），@since 4.0
			case ACTION_APPEND3:
			{
				JSONObject nwObj = JSONObject.parseObject(newDataJSON);
				String uid = nwObj.getString("user_uid");    // 要查询用户的uid
				String resType = nwObj.getString("res_type");// 要查询资源类型：0 表示相片、1 表示个人介绍语音

				Vector<Vector> vecs = db.queryData("select resource_id" +
						",res_file_name,res_human_size,res_size,view_count,create_time" +
						" from missu_users_binary where user_uid='"
						+uid+"' and res_type="+resType+" order by create_time desc");
				return JSON.toJSONString(vecs);
			}
			// 【接口1008-10-22】查询个人相册预览图片数据列表（目前用于客户端个
			// 人信息查看界面中显示照片预览列表时使用），@since 4.0
			case ACTION_APPEND4:
			{
				JSONObject nwObj = JSONObject.parseObject(newDataJSON);
				String uid = nwObj.getString("user_uid");    // 要查询用户的uid

				// 根据db类型的不同，决定选取前4行结果的sql该怎么写
				boolean isSQLServer = DBShell.isSQLServer();
				boolean isMySQL = DBShell.isMySQL();
				Vector<Vector> vecs = db.queryData("select "+(isSQLServer?"top 4":"")+" res_file_name" +
							" from missu_users_binary" +
							" where user_uid="+uid+" and res_type=0" +
							" order by create_time desc "+(isMySQL?"limit 0,4":""));
				return JSON.toJSONString(vecs);
			}
			
			default:
				throw EndsExceptionFactory.INVALID_ACTION_ID(action_id);
		}
	}
	
	/** 
	 * 维护通用数据查询接口的管理.
	 * 
	 * @param action_id 维护类型，SysConstant中的操作类型常量
	 * @param newData 客端发送过来的本次修改新数据(可能为空，理论上与oldData不会同时空）
	 * @param oldData 客端发送过来的本次修改前的老数据(可能为空，理论上与newData不会同时空）
	 * @param userName 操作者的用户名
	 * @return 返回给客户端的对象
	 */
	public String maintainCommonQueryJobDispatcher(int action_id
			, String newDataJSON, Object _oldData, User user) throws Exception
	{
		Vector sqls = new Vector();
		Vector values = new Vector();
		
		switch (action_id)
		{
			// 【接口1008-26-7】: 具体说明，详见“RainbowChat4.0-HttpRest接口手册.pdf”！
			// 用途说明：用于查询（构造）客户端界面上首页“消息”列表；
			// 数据说明：本列表的数据，取自被查询用户与所有聊天对象的最近一次聊天记录（消息内容、消息类型、消息时间等）；
			// 适用范围：当对接RainbowChat-Web产品时，本接口将被用于Web端（虽然移动端等也可以用，但没有必要，原因为
			//          Android或iOS端有本地缓存数据，直接本地构建这个数据列表即可，无需连服务端查询）；
			case ACTION_APPEND1:
			{	
				// 客户端传过来的数据
				JSONObject nwObj = JSONObject.parseObject(newDataJSON);
				
				// 被查询者的uid
				String uid = nwObj.getString("uid");
				// 数据查询范围的起始时间（此参数为空表示查询时不区分时间范围），形如：“2019-01-01 10:02:02”
				String startTime = nwObj.getString("starttime"); 
				
				// 是否需要查询时间范围条件
				boolean hasTimeCondition = (!CommonUtils.isStringEmpty(startTime, true));
				// 额外的时间日期SQL过滤条件
				String timeFilter = (hasTimeCondition
						?" (msg_time BETWEEN '"+startTime+"' and "+DBDepend.getDefaultDatetimeFunc()+")"
						:"");

				StringBuilder sb = new StringBuilder();
				
				//## 首先构建要查询数据的SQL语句：
				//   此语句由多层子查询构成，因为要在原先简单的数据库表结构的基础上实现这种
				//   较复杂的数据查询逻辑，且需考虚查询性能的情况下，子查询是必须要使用的。
				
				/* 用所有“我”与对方或我参与的群聊的最近一次聊天消息记录的id，再级连查出这些聊天记录的完整内容 */
				// 提示：以下是一个非常复杂的嵌套子查询SQL语句，目的是力求一个完整SQL查出需要的数据，减少数据库查询次数
				sb.append("SELECT ")
				.append("	  XX.fid" )           // 结果字段1：聊天对象的uid
				
				// 结果字段2：聊天对象的昵称
//				.append("	, ZZ.nickname" )      // v7.3前的写法
//				.append("	, (select "+DBDepend.getNVLFunc("remark", "ZZ.nickname")+" from missu_roster where user_uid='400071' and friend_user_uid=ZZ.user_uid) as remark " )      
				.append("	, "+DBDepend.getNVLFunc("(select remark from missu_roster where user_uid="+uid+" and friend_user_uid=ZZ.user_uid)", "ZZ.nickname")+" as remark " )      
				
				.append("	, YY.msg_type" )      // 结果字段3：聊天消息类型，见MsgBodyRoot类中的定义
				.append("	, YY.msg_content" )   // 结果字段4：聊天消息内容
				.append("	, YY.msg_time" )      // 结果字段5：聊天时间
				.append("	, YY.msg_time2" )     // 结果字段6：聊天时间（java时间戳长整形）
				.append("	, YY.src_uid ")       // 结果字段7：此行聊天记录的源uid（保留字段，暂未用到）
							/* 结果字段8：判断聊天对象是否是“我”的好友（当VV.user_uid为null时即表示无匹配记录），返回值是0（否）或1（是）哦 */
//				.append("	, (case VV.user_uid when null then 0 else 1 end) as isFriends ")
							// 注意：sql server中，遇到null值时，如果case xxxx when null这种写法，则即便 xxxx是null,得到的结果也是1，这很可能是sql server的bug！
				.append("	, (case when VV.user_uid is null then 0 else 1 end) as isFriends ")
//				.append("	/*, case when XX.fid=YY.src_uid then 1 else 0 end as isCommingMsg*/ /* 表示是对方发给“我”的消息，否则是“我”发给“他”的 */")
				.append("	, YY.chat_type ")      // 结果字段9：聊天模式，详见 MsgBodyRoot类常量
				.append("	, CASE YY.chat_type WHEN 2 then dest_uid else null end as g_id ")// 结果字段10：群id（群聊消息时有意义）
				.append("	, WW.g_name ")         // 结果字段11：群名称（群聊消息时有意义）
				.append("FROM ")
				.append("(")
							/* 再将“我”的所有聊天记录按照聊天者进行分组，并对每个分组按聊天记录id进行逆序排序，并取出每个分组中id最大的记录（即我与每个人最近一次的聊天消息） */
				.append("	SELECT X.fid AS fid,max(X.collect_id) AS collect_id FROM ")
				.append("	(")
								/* [单聊消息]先将所有“我”主动聊天的人，或者“我”被动聊天的人的消息，进行并集，合并成我的所有聊天记录 */
				.append("		SELECT dest_uid AS fid,collect_id FROM missu_user_msgs_collect WHERE src_uid="+uid+" AND chat_type IN(0,1) ")
				.append("		UNION ")
				.append("		SELECT src_uid AS fid,collect_id FROM missu_user_msgs_collect WHERE dest_uid="+uid+" AND chat_type IN(0,1) ")
				.append("		UNION")
								/* [群聊消息]查询我所在所有群组收到的消息（本SQL的where查询条件如用IN则更简单，但EXISTS语句性能更好，所以用之） */
				.append("		SELECT dest_uid AS fid,collect_id FROM missu_user_msgs_collect AAA ")
				.append("		WHERE EXISTS (")
				.append("			select 1 from (")
				.append("				select A.g_id FROM (select g_id from group_members where user_uid="+uid+") A join group_base B on A.g_id=B.g_id and B.g_status=1 ")
				.append("			)AA where AA.g_id=dest_uid ")
				.append("		) ")
							    // @since 4.5b20190401: 额外条件实现聊天记录只查到该用户能看到的聊天时间之后（用于防止用户能看到入群前的聊天记录，以及实现首页“消息”中“删除”消息的功能）
				.append("		and EXISTS(select 1 from group_members B where B.g_id=AAA.dest_uid and B.user_uid="+uid+" and (AAA.msg_time >= B.msg_time_start or B.msg_time_start is null)) ")
				.append("		and chat_type=2 ")
				.append("	) X ")
				.append("	GROUP BY X.fid ")
//				.append("	/*ORDER BY X.collect_id desc*/")
				.append(") XX ")
				.append("JOIN missu_user_msgs_collect YY ON XX.collect_id = YY.collect_id ")
				.append("LEFT JOIN missu_users ZZ ON XX.fid = ZZ.user_uid ")
				.append("LEFT JOIN group_base WW ON (CASE YY.chat_type WHEN 2 then dest_uid else null end)=WW.g_id ")
				.append("LEFT JOIN missu_roster VV ON VV.user_uid="+uid+" AND XX.fid=VV.friend_user_uid ")
				.append("WHERE "+timeFilter)
				.append("ORDER BY YY.msg_time DESC ");
				
				Vector<Vector> vecs = db.queryData(sb.toString());
				
				return JSON.toJSONString(vecs);
				
			}
			
			// 【接口1008-26-8】:查询聊天记录（已用于：Web端）。具体说明，详见“RainbowChat4.0-HttpRest接口手册.pdf”！
			//  支持：一对一聊天记录的返回（即“我”与指定uid用户的记录）、群聊记录的返回
			//  · 用途说明：用于从服务端查询最近一段时间的聊天记录；
			//  · 数据说明：因服务端存在超期聊天记录将被转储的功能，因而本接口的数据只包含转储超时期限内的；
			//  · 适用范围：当对接RainbowChat-Web产品时，本接口将被用于Web端（虽然移动端等也可以用，但没有
			//             必要，原因为Android或iOS端有本地缓存数据，直接本地查询即可，无需连服务端查询）。
			case ACTION_APPEND2:
			{
				// 客户端传过来的数据
				JSONObject nwObj = JSONObject.parseObject(newDataJSON);
				
				String gid = nwObj.getString("gid");   // 查群聊记录必填参数：要查询的群id
				
				String luid = nwObj.getString("luid"); // 查单聊或群聊记录必填参数：local uid（即“我”的uid）
				
				String ruid = nwObj.getString("ruid"); // 查单聊记录选填参数：remote uid（即“对方”的uid）
				
				// true表示当前要查询的是群聊消息记录，否则表示要查询的一对一聊天记录
				boolean isQueryGroupChatting = (!CommonUtils.isStringEmpty(gid, true));
				
				String orderby = nwObj.getString("orderby");     // 排序方式： 1 表示按消息时间DESC逆序，0 表示按消息时间ASC顺序排序
				
				String startTime = nwObj.getString("starttime"); // 聊天记录查询范围的起始时间（为空表示不区分时间范围），形如：“2019-01-01 10:02:02”
				String endTime = nwObj.getString("endtime");     // 聊天记录查询范围的结束时间（为空表示查询截止当前时间），形如：“2019-01-01 10:02:02”
				
				// 是否需要查询时间范围条件
				boolean hasTimeCondition = (!CommonUtils.isStringEmpty(startTime, true));
				
				StringBuilder sb = new StringBuilder();
				
				String filter = "1=1";
				
				// 查询过滤条件
				if(isQueryGroupChatting)
				{
					// 群聊查询条件
					filter = "(chat_type=2 and dest_uid="+gid+" and " +
					         // @since 4.5b20190401: 额外条件实现聊天记录只查到该用户能看到的聊天时间之后（用于防止用户能看到入群前的聊天记录，以及实现首页“消息”中“删除”消息的功能）
							 "	EXISTS(select 1 from group_members b where b.g_id='"+gid+"' and b.user_uid='"+luid+"' and (a.msg_time >= b.msg_time_start or b.msg_time_start is null))" +
							 ")";
				}
				else
				{
					// 单聊查询条件
					filter = "((src_uid='"+luid+"' and dest_uid='"+ruid
							+"') or (src_uid='"+ruid+"' and dest_uid='"+luid+"'))";
				}
				
				// 额外的时间日期过滤条件
				String timeFilter = (hasTimeCondition
						?"and (msg_time BETWEEN '"+startTime+"' and "+(CommonUtils.isStringEmpty(endTime, true)?DBDepend.getDefaultDatetimeFunc():"'"+endTime+"'")+")"
						:"");
				
				// 组合成完整的sql语句
				sb.append("select ")
					.append("	src_uid, dest_uid, chat_type, msg_type, msg_content")
					// 将msg_time2字段的无时区时间戳传给客户端，以便客户端按其所有时区显示为正确的时间
					.append("	,msg_time2 ")
//					.append("	,DATE_FORMAT(msg_time,'"+DATETIME_FORMAT_FROM_MYSQL_YYYY_MM_DD_HHMMSS+"') as msg_time")
					.append("	,fingerprint ")
					.append("	,parent_fp ")
					.append("from missu_user_msgs_collect a ")
					.append("where "+filter+" "+timeFilter)
					.append("order by collect_id "+("1".equals(orderby)?"desc":"asc"));// 使用collect_id排序比msg_time更精确（因为理论上存在时间重复的可能性）
				
				Vector<Vector> vecs = db.queryData(sb.toString());
				
				return JSON.toJSONString(vecs);
			}
			
			default:
				throw EndsExceptionFactory.INVALID_ACTION_ID(action_id);
		}
	}
	
	/**
	 * 准备好删除单聊（一对一好友、一对一陌生人）的聊天记录和离线消息的SQL。
	 * <p>
	 * 说明：单聊消息不是真的从物理上删除，只是逻辑上实现“删除”而已（即转储到存档表里而已）。
	 * 
	 * @param sqls
	 * @param values
	 * @param myUid 聊天记录中的“我”
	 * @param friendUid 聊天记录中的“好友”
	 * @throws Exception
	 * @since 4.5
	 */
	private static void prepareDeleteSingleChattingMsg(Vector sqls
			, Vector values, String myUid, String friendUid) throws Exception
	{
		//********************* 先尝试存档聊天记录 ************************//
		// 好友记录的SQL条件
		String conditionSQL = "((src_uid="+myUid+" and dest_uid="+friendUid+")" +
				" or (src_uid="+friendUid+" and dest_uid="+myUid+"))";
		if(LogicProcessor2.db.queryData("select 1 from missu_user_msgs_collect where "+conditionSQL).size()>0)
		{			
			// 先将聊天记录先插入到存档表中
			sqls.add(TimmingRestoreMessages.COPY_CHATTING_MESSAGES_TO_ACHIVED_SQL+" where "+conditionSQL);
			values.add(new Object[]{});

			// 再将聊天记录从聊天记录表中移除
			sqls.add(TimmingRestoreMessages.DELETE_CHATTING_MESSAGES_SQL+" where "+conditionSQL);
			values.add(new Object[]{});
		}
		
		//********************* 再尝试删除未拉取的离线消息 ************************//
		// 离线消息的SQL条件
		String conditionSQL2 = "((user_uid="+myUid+" and friend_user_uid="+friendUid+")" +
				" or (user_uid="+friendUid+" and friend_user_uid="+myUid+"))";
		if(db.queryData("select 1 from missu_offline_history where "+conditionSQL2).size()>0)
		{
			// 直接将超出期限的离线消息从离线消息表中移除
			sqls.add("DELETE FROM missu_offline_history where "+conditionSQL2);
			values.add(new Object[]{});
		}
	}
	
	/**
	 * 从查询出来的数据库行中解析并构建离线好友请求数据对象（供客户端使用）。
	 * 
	 * @param row
	 * @return
	 * @throws Exception
	 */
	private static RosterElementEntity constructOfflineAddFriendReqObj(Vector row) throws Exception
	{
		if(row != null)
		{
			String reqSrcUid = (String)row.get(0);
			String reqTime = (String)row.get(1);
			String saySomethingFromHim = (String)row.get(2);
			
			RosterElementEntity ree = LogicUtils.queryUserInfoByUID(reqSrcUid, null);
			if(ree != null)
			{
				// 自20140213 RainbowChat2.2（含）以后ex1作为服务端转发加好友请求（给B）时存放“验证说明文本”使用（包括离线消息）
				ree.setEx1(MyControllerJSONP.JSONSave(saySomethingFromHim));
				// 自20171225 RainbowChat4.2（含）以后ex10作为服务端转发加好友请求（给B）时存放“好友请求发起时间戳”使用（包括离线消息）
				// 自20180507 RBv4.3以后，本字段存放的是时间戳，而非人类可读的时间字串
				ree.setEx10(reqTime);
			}
			
			return ree;
		}
		
		return null;
	}
}
